/**
 * 文件操作
 */

const fs = require("fs");
const path = require("path");

/**
 * 
    文件描述符：
      1、基于回调的 fs.open() 和同步 fs.openSync() 方法打开一个文件并分配一个新的文件描述符。 分配后，文件描述符可用于从文件读取数据、向文件写入数据或请求有关文件的信息。
      2、对于每个进程，内核维护一个当前打开的文件和资源表。 每个打开的文件都分配了一个简单的数字标识符，称为文件描述符。 
      3、操作系统限制在任何给定时间可能打开的文件描述符的数量，因此在操作完成时关闭描述符至关重要。 否则将导致内存泄漏，最终导致应用程序崩溃。
      4、文件描述符不会自动关闭
 */


/*
  fs.open(path[, flags[, mode]], callback)  以异步的方式打开文件。
  path: 将打开的文件路径
  flag: 默认 'r' (以读取模式打开, 如果文件不存在，会报错)
　mode: 设置文件模式（权限和粘滞位），但前提是文件已创建，默认为可读可写, 在 Windows 上，只能操作写入权限
　callback: 回调有两个参数 (err, fd)，传递异常参数err; fd: 文件描述符 
*/
function _open() {
  fs.open( './test/testfile', 'r', (err, fd) => {
    if (!err) {
      console.log("打开文件成功");
      fs.close(fd, () => { // 关闭打开的文件
        console.log("操作完毕，关闭文件") 
      })
    } else {
      console.log("打开文件失败: ", err);
    }
  })
}

// _open();

/*
  fs.openSync(path[, flags[, mode]])  以同步的方式打开文件。
  path: 将打开的文件路径
  flag: 默认 'r' (以读取模式打开, 如果文件不存在，会报错)
　mode: 设置文件模式（权限和粘滞位），但前提是文件已创建，默认为可读可写, 在 Windows 上，只能操作写入权限
  返回文件操作符
*/
function _openSync() {
  const fd = fs.openSync( './test/testfile', 'r')
  fs.close(fd, () => { // 关闭打开的文件
    console.log("操作完毕，关闭文件") 
  })
}

// _openSync();

/*
  fs.read(fd,buffer,offset,length,position,callback)  异步读取文件数据
  fd 文件描述符，必须接收fs.open()方法中的回调函数返回的第二个参数。
  buffer 是存放读取到的数据的Buffer对象。
  offset 指定 向buffer中存放数据的起始位置。
  length 指定 读取文件中数据的字节数。
  position 指定 在文件中读取文件内容的起始位置。
  callback 回调函数，(err, bytesRead, buffer), err 用于抛出异常; bytesRead 从文件中读取内容的实际字节数; buffer 被读取的缓存区对象。
*/
function _read() {
  fs.open( './test/testfile', 'r', (err, fd) => {
    if (!err) {
      console.log("打开文件成功");
      const buffer = Buffer.alloc(255);

      fs.read(fd, buffer, 0, 255, 0, (_err, bytesRead, buffer) => {
        if (_err) {
          console.log("读取文件失败");
          throw err;
        }
        // 打印buffer中存入的数据
        console.log(bytesRead, buffer.slice(0, bytesRead).toString())
      })

      fs.close(fd, () => { // 关闭打开的文件
        console.log("操作完毕，关闭文件") 
      })
    } else {
      console.log("打开文件失败: ", err);
    }
  })
}

// _read();

/*
  fs.read(fd,buffer,offset,length,position)  同步读取文件数据
  fd 文件描述符，必须接收fs.open()方法中的回调函数返回的第二个参数。
  buffer 是存放读取到的数据的Buffer对象。
  offset 指定 向buffer中存放数据的起始位置。
  length 指定 读取文件中数据的字节数。
  position 指定 在文件中读取文件内容的起始位置。
  返回 bytesRead 的数量。
*/
function _readSync() {
  fs.open( './test/testfile', 'r', (err, fd) => {
    if (!err) {
      console.log("打开文件成功");
      const buffer = Buffer.alloc(255);

      const bytesRead = fs.readSync(fd, buffer, 0, 255, 0)
      console.log(bytesRead)

      fs.close(fd, () => { // 关闭打开的文件
        console.log("操作完毕，关闭文件") 
      })
    } else {
      console.log("打开文件失败: ", err);
    }
  })
}

// _readSync();

/*
  fs.readFile(path[, options], callback) 异步读取文件的全部内容
  path: 文件名或文件描述符
  options: encoding 默认null; flag, 默认 'r'; signal, 允许中止正在进行的读取文件
  callback: 回调 （err, data) err 用于抛出异常; data: 数据
*/
function _readFile() {
  fs.readFile( './test/testfile', {encoding:"utf-8"} , (err, data) => {
    if (!err) {
      console.log(data);
    } else {
      console.log("读取文件内容失败: ", err);
    }
  })
}

// _readFile();

/*
  fs.readFileSync(path[, options]) 同步读取文件的全部内容
  path: 文件名或文件描述符
  options: encoding 默认null; flag, 默认 'r'; signal, 允许中止正在进行的读取文件
  返回文件的全部内容。
*/
function _readFileSync() {
  const data = fs.readFileSync( './test/testfile', {encoding:"utf-8"})
  console.log(data);
}

// _readFileSync();


/*
  fs.rename(oldPath, newPath, callback) 将 oldPath 处的文件异步重命名为作为 newPath 提供的路径名。
    如果 newPath 已经存在，则它将被覆盖。 如果在 newPath 中有目录，则会引发错误。
  callback: 回调 （err) err 用于抛出异常
*/
function _rename() {
  fs.rename( './test/testfile',  './test/testfile2', (err) => {
    if (!err) {
      console.log("重命名文件成功");
    } else {
      console.log("重命名文件失败: ", err);
    }
  })
}

// _rename();

/*
  fs.renameSync(oldPath, newPath) 将 oldPath 处的文件同步重命名为作为 newPath 提供的路径名。
    如果 newPath 已经存在，则它将被覆盖。 如果在 newPath 中有目录，则会引发错误。
    返回 undefined。
*/
function _renameSync() {
  const res = fs.renameSync( './test/testfile',  './test/testfile2');
  console.log(res);
}

// _renameSync();


/*
  fs.stat(path[, options], callback) 以异步方式读取文件信息
  path: 将读取的文件路径 string> | <Buffer> | <URL>
  options: 返回的 <fs.Stats> 对象中的数值是否应为 bigint。 默认值: false.
  callback: 回调 （err, stats) err 用于抛出异常; stats 是 <fs.Stats> 对象。
*/
function _stat() {
  fs.stat( './test/testfile', (err, stats) => {
    if (!err) {
      console.log("读取文件信息成功, ", stats);
    } else {
      console.log("读取文件信息失败: ", err);
    }
  })
}

// _stat();

/*
  fs.statSync(path[, options], callback) 以同步方式读取文件信息
  path: 将读取的文件路径 string> | <Buffer> | <URL>
  options: 
    bigint: 返回的 <fs.Stats> 对象中的数值是否应为 bigint。 默认值: false.
    throwIfNoEntry: 如果不存在文件系统条目,是否会抛出异常, 而不是返回 undefined  默认值: true.
  返回: <fs.Stats>
*/
function _statSync() {
  const stats = fs.statSync( './test/testfile')
  console.log(stats);
}

// _statSync();

/*
  fs.unlink(path, callback) 异步删除文件或符号链接。
  path: 将要删除的文件或符号链接
  callback: 回调 （err, stats) err 用于抛出异常
*/
function _unlink() {
  fs.unlink( './test/testfile', (err) => {
    if (!err) {
      console.log("删除文件成功");
    } else {
      console.log("读取文件失败: ", err);
    }
  })
}

// _unlink();

/*
  fs.unlinkSync(path) 同步删除文件或符号链接。
  path: 将要删除的文件或符号链接
  返回 undefined
*/
function _unlinkSync() {
  const res = fs.unlinkSync( './test/testfile')
  console.log(res);
}

// _unlinkSync();


/**
 * 使用 fs.watch() 比 fs.watchFile 和 fs.unwatchFile 更有效, 应尽可能使用 fs.watch 
 * @description: 
 * fs.watch(filename[, options][, listener]) 异步监视文件变化
 * 在 Windows 上，如果监视目录被移动或重命名，则不会触发任何事件。 删除监视目录时报 EPERM 错误
 * @param {*}
 * filename: 要监视的文件或目录。
 * options： 
 *    persistent：指示只要正在监视文件，进程是否应继续运行。 默认值: true
 *    recursive: 指示是应监视所有子目录，还是仅监视当前目录, 且仅在受支持的平台上有效。 默认false
 *    encoding: 指定用于传入给监听器的文件名的字符编码。 默认值: 'utf8'
 *    signal: 允许使用中止信号关闭监视器
 * listener:
 *    eventType: <string> eventType 是 'rename' 或 'change'
 *    filename: <string> | <Buffer> 是触发事件的文件的名称。
 * @return {*}
 * 返回: <fs.FSWatcher>
 */
function _watch() {
  fs.watch( './test/testfile', (eventType, filename) => {
    console.log("eventType: ", eventType);
    console.log("filename: ", filename);
  })
}

// _watch();

/**
 * @description: 
 * fs.watchFile(filename[, options], listener)
 * filename <string> | <Buffer> | <URL>
 * @param {*}
 * options:
 *    bigint 默认值: false
 *    persistent 默认值: true 只要文件正在被监视，进程是否应该继续运行
 *    interval 默认值: 5007 以毫秒为单位轮询目标的频率
 * listener:
 *    current:
 *    previous:
 * @return {*}
 * 返回: <fs.StatWatcher
 */
function _watchFile() {
  fs.watchFile( './test/testfile', (current, previous) => {
    console.log("current: ", current);
    console.log("previous: ", previous);
  })
}

// _watchFile();

let listener = function (cur, prev) {
  console.log("文件有修改")
}
var filename = path.resolve('./test1/testfile');

function _watchFile2() {
  fs.watchFile(filename, { interval: 100 }, listener)
}

/**
 * @description: 
 * fs.unwatchFile(filename[, listener]) 
 *    如果指定了 listener，则仅删除该特定监听器。 否则，所有监听器都将被删除
 * @param {*}
 *    filename <string> | <Buffer> | <URL>
 *    listener <Function> 可选，先前使用 fs.watchFile() 附加的监听器。
 * @return {*}
 */
function _unwatchFile() {
  fs.unwatchFile(filename, listener)
}

// _watchFile2();

// setTimeout(() => { // 5s后取消监视
//   _unwatchFile(); 
// }, 5000)

/**
 * @description: 以异步方式修改文件访问时间、修改时间
 * fs.utimes(path, atime, mtime, callback)
 * @param {*}
 * path: <string> | <Buffer> | <URL>
 * atime: 访问时间 值可以是代表 Unix 纪元时间的数字、Date、或数字字符串（例如 '123456789.0'）
 * mtime: 修改时间 值可以是代表 Unix 纪元时间的数字、Date、或数字字符串（例如 '123456789.0'）
 * callback: 回调 (err)
 * @return {*}
 */
function _utimes() {
  fs.utimes('./test/testfile', new Date("1996-10-03 12:32:56"), new Date("2996-10-03 12:32:56"), (err) => {
    if (!err) {
      console.log("修改文件时间成功");
    } else {
      console.log("修改文件时间失败: ", err);
    }
  })
}

// _utimes();

/**
 * @description: 以同步方式修改文件访问时间、修改时间
 * fs.utimesSync(path, atime, mtime)
 * @param {*}
 * path: <string> | <Buffer> | <URL>
 * atime: 访问时间 值可以是代表 Unix 纪元时间的数字、Date、或数字字符串（例如 '123456789.0'）
 * mtime: 修改时间 值可以是代表 Unix 纪元时间的数字、Date、或数字字符串（例如 '123456789.0'）
 * @return {*}
 * 返回undefined
 */
 function _utimesSync() {
  fs.utimesSync('./test/testfile', new Date("1997-10-03 12:32:56"), new Date("9996-10-03 12:32:56"))
 }

//  _utimesSync();


/**
 * @description: 以异步方式关闭文件操作
 * fs.close(fd[, callback])
 * @param {*}
 * fd: 文件操作符
 * callback: 回调 (err) 传递错误参数
 * @return {*}
 */
function _close() {
  fs.open( './test/testfile', 'r', (err, fd) => {
    if (!err) {
      console.log('打开文件成功');
      fs.close(fd, (_err) => { // 关闭打开的文件
        if(!_err) {
          console.log('操作完毕，关闭文件');
        } else {
          console.log('关闭文件失败: ', _err); 
        }
      })
    } else {
      console.log('打开文件失败: ', err);
    }
  })
}

// _close();

/**
 * @description: 以同步方式关闭文件操作
 * fs.closeSync(fd)
 * @param {*}
 * fd: 文件操作符
 * @return {*}
 * 返回 undefined
 */
 function _closeSync() {
  fs.open('./test/testfile', 'r', (err, fd) => {
    if (!err) {
      console.log('打开文件成功');
      fs.closeSync(fd);
    } else {
      console.log('打开文件失败: ', err);
    }
  })
}

// _closeSync();


/**
 * @description: 异步将数据追加到文件，如果该文件尚不存在，则创建该文件
 * fs.appendFile(path, data[, options], callback)
 * @param {*}
 * path: 将要追加的文件名或文件描述符
 * data <string> | <Buffer
 * options
 *    encoding 默认值: 'utf8'
 *    mode 默认值: 0o666
 *    flag 默认值: 'a'
 * callback: 回调 (err) 传递异常参数
 * @return {*}
 */
function _appendFile() {
  fs.appendFile("./test/testfile", "my heart", (err) => {
    if (!err) {
      console.log('数据追加至文件成功');
    } else {
      console.log('数据追加至文件失败: ', err);
    }
  })
}

// _appendFile();

/**
 * @description: 将数据同步追加到文件中，如果文件尚不存在则创建该文件。
 * fs.appendFileSync(path, data[, options])
 * @param {*}
 * path: 将要追加的文件名或文件描述符
 * data <string> | <Buffer
 * options
 *    encoding 默认值: 'utf8'
 *    mode 默认值: 0o666
 *    flag 默认值: 'a'
 * @return {*}
 */
 function _appendFileSync() {
  fs.appendFileSync("./test/testfile", "my heart")
 }

// _appendFileSync();

/**
 * @description: 以异步的方式更改文件权限。
 * fs.chmod(path, mode, callback)
 * @param {*}
 * path: 将要追加的文件名或文件描述符
 * mode <string> | <integer>
 * callback: 回调 (err), 传递
 * @return {*}
 */
 function _chmod() {
  fs.chmod("./test/testfile", 0o777, (err) => {
    if (!err) {
      console.log('更改文件权限成功');
    } else {
      console.log('更改文件权限失败: ', err);
    }
  })
 }

//  _chmod();

 /**
 * @description: 以异步的方式更改文件权限。
 * fs.chmodSync(path, mode, callback)
 * @param {*}
 * path: 将要追加的文件名或文件描述符
 * mode <string> | <integer>
 * callback: 回调 (err), 传递
 * @return {*}
 */
 function _chmodSync() {
  fs.chmodSync("./test/testfile", 0o577)
 }

 _chmodSync();